package io.gitee.caoxiaoyu97.log4j.defender.provider;

import io.gitee.caoxiaoyu97.log4j.defender.constants.DefenderConstants;
import io.gitee.caoxiaoyu97.log4j.defender.message.log.AbstractLogDefender;
import io.gitee.caoxiaoyu97.log4j.defender.message.log.RegexKeyDefender;
import io.gitee.caoxiaoyu97.log4j.defender.message.log.RegexValueDefender;
import io.gitee.caoxiaoyu97.log4j.defender.message.log.SpringBeanDefender;
import io.gitee.caoxiaoyu97.log4j.defender.message.log.StringFieldDefender;
import io.gitee.caoxiaoyu97.log4j.defender.properties.DefenderProperties;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

public class LogDefenderProvider {
    private static final Logger logger = LoggerFactory.getLogger(LogDefenderProvider.class);
    private static LogDefenderProvider provider;
    private AtomicBoolean isEnable = new AtomicBoolean(false);
    private Map<String, AbstractLogDefender> defender;
    protected Integer maxSearchSize = Integer.valueOf(10000);
    private Set<String> excludePkgPrefix = new HashSet<>();
    private Set<String> includePkgPrefix = new HashSet<>();

    public static LogDefenderProvider getInstance() {
        if (null == provider) {
            synchronized (LogDefenderProvider.class) {
                if (null == provider) {
                    provider = new LogDefenderProvider();
                }
            }
        }

        return provider;
    }

    public LogDefenderProvider init(DefenderProperties properties) {
        this.isEnable.set(false);
        this.defender = new HashMap<>(4);
        String type = properties.getType();
        if (!StringUtils.isEmpty(type)) {
            String[] types = DefenderConstants.DEFAULT_SPLIT_PATTERN.split(type.toLowerCase());
            for (String key : types) {
                switch (key.trim()) {
                    case "bean":
                        this.defender.putIfAbsent(key.trim(), new SpringBeanDefender(properties.getBean()));
                        break;
                    case "stringfield":
                        this.defender.putIfAbsent(key.trim(), new StringFieldDefender(properties.getStringfield()));
                        break;
                    case "regexkey":
                        this.defender.putIfAbsent(key.trim(), new RegexKeyDefender(properties.getRegexkey()));
                        break;
                    case "regexvalue":
                        this.defender.putIfAbsent(key.trim(), new RegexValueDefender(properties.getRegexvalue()));
                        break;
                }

            }
        }
        this.includePkgPrefix.addAll(properties.getIncludepkgs().values());
        this.excludePkgPrefix.addAll(properties.getExcludepkgs().values());
        this.maxSearchSize = properties.getMaxsearchsize();
        this.isEnable.set(true);
        logger.info("LogDefender initialized.");
        return this;
    }

    public boolean isEnabled() {
        return this.isEnable.get();
    }

    public boolean isInLoggerScope(String loggerName) {
        loggerName = StringUtils.isEmpty(loggerName) ? "root" : loggerName;
        if (this.excludePkgPrefix.stream().anyMatch(loggerName::startsWith)) {
            return false;
        }

        if (this.includePkgPrefix.isEmpty() || this.includePkgPrefix.stream().anyMatch(loggerName::startsWith)) {
            return true;
        }

        return false;
    }

    public StringBuilder doReplace(StringBuilder inputOrigin) {
        if (StringUtils.isEmpty(inputOrigin) || inputOrigin.length() > this.maxSearchSize.intValue() ||
                ObjectUtils.isEmpty(this.defender)) {
            return inputOrigin;
        }

        StringBuilder input = inputOrigin;
        for (AbstractLogDefender replacer : this.defender.values()) {
            input = replacer.desensitize(input);
        }
        return input;
    }
}
